Add PrecomputedInsulinInput for efficient multi-step prediction sweeps + parallelize glucose-effects#29
Open
ps2 wants to merge 6 commits into
Open
Add PrecomputedInsulinInput for efficient multi-step prediction sweeps + parallelize glucose-effects#29ps2 wants to merge 6 commits into
ps2 wants to merge 6 commits into
Conversation
Introduces PrecomputedInsulinInput and a new generatePrediction overload that accepts pre-annotated dose data, enabling significant speedups for historical back-testing / evaluation sweeps. The key bottleneck in a dense prediction sweep is doses.annotated(with: basal), which is O(doses × basalSegments) and was called from scratch at every step. Between adjacent 5-min steps the dose list changes only at its edges; the annotation of every dose in the middle is identical. Changes: - Sources/LoopAlgorithm/Insulin/PrecomputedInsulinInput.swift (new) PrecomputedInsulinInput struct holding pre-annotated doses and an optional pre-built insulinEffects timeline. Includes a convenience .build() factory. - Sources/LoopAlgorithm/LoopAlgorithm.swift New generatePrediction(start:glucoseHistory:precomputedInsulin:carbEntries: sensitivity:carbRatio:...) overload. Skips annotated(with:) entirely; optionally skips glucoseEffects() when insulinEffects is pre-supplied. - Sources/LoopAlgorithm/Glucose/GlucoseEffect.swift Add Sendable conformance (struct with value-type fields, safe). - Tests/LoopAlgorithmTests/PrecomputedInsulinInputTests.swift (new) 3 tests verifying the new overload produces output matching the standard path (bit-identical for annotation-only, count-identical + clinically equivalent for pre-built effects). Expected speedup for a 7-day sweep at 5-min step (~2016 calls): annotation bypass alone: ~40-60% wall-clock reduction + effects cache (fixed ISF): additional ~20-30%
Split the API into two explicit steps so ISF sweeps pay annotation cost
exactly once across all multipliers:
annotate(doses:basal:) → ISF-independent, build once
.withEffects(sensitivity:from:to:) → ISF-dependent, once per multiplier
Correct ISF sweep pattern:
let base = PrecomputedInsulinInput.annotate(doses: doses, basal: basal)
for multiplier in isfMultipliers {
let input = base.withEffects(sensitivity: scale(sensitivity, by: multiplier))
// run ~2016 steps with input — no annotation, no per-step glucoseEffects
}
Cost breakdown for 10-multiplier × 7-day sweep (n≈2016 steps each):
Before: annotated(with:) + glucoseEffects() called 20160× each
After: annotated(with:) called 1×, glucoseEffects() called 10×
Also adds testISFSweepPattern verifying bit-identical output across
multipliers [0.7, 0.8, ..., 1.3] vs the standard generatePrediction path.
Enables EvalCore to slice pre-annotated doses to the per-step lookback window without re-annotating. Uses binary search on startDate + linear filter on endDate (arrays are ~100-200 entries, linear endDate scan is negligible). Also cleans up the unused private partition helper (now only used by sliced).
Downstream callers (LoopEval bench engine) need to compute dose recommendations from a forecast without going through the full run() API, which re-computes insulin effects. Making insulinCorrection, recommendTempBasal, and recommendAutomaticDose public lets them do that efficiently using already-computed predictions. Enables delivery-based ODR/UDR metrics in LoopEval that compare the actual insulin Loop would deliver across two configurations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the sequential reduce loop with DispatchQueue.concurrentPerform over per-step increments, then a final cumsum. Per-step contributions are independent until the final summation, so this scales with available cores. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ted-insulin-effects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
PrecomputedInsulinInput— a precomputed, per-dose annotated insulin-effect input designed for tools that run Loop's prediction repeatedly over the same data with varying ISF/sensitivity assumptions (e.g., evaluation tools doing per-step replay over multi-week windows).Companion change:
glucoseEffectsMidAbsorptionISFis parallelized viaDispatchQueue.concurrentPerformsince each delta-step's increment is independent until the final cumsum. Same behavior, ~5-10× faster on multi-core machines.Commits
sliced(from:to:)for per-step dose window slicing — slim the annotated set down to the window relevant to a single prediction callDispatchQueue.concurrentPerformacross the time-step axisWhy
Used by LoopEval (a closed-loop sim that runs Loop's prediction at every 5-min step across multi-week windows) where the same dose history is re-used with different sensitivity multipliers. Pre-annotating each dose's per-component contribution lets the per-step sweep reduce to O(active doses × ISF segments) without re-doing the dose-decomposition work.
Test plan
xcrun swift test)PrecomputedInsulinInputTests.swiftwith 4 tests:testPrecomputedAnnotationMatchesStandard— equivalence vsglucoseEffectsfor arbitrary dose historiestestISFSweepPattern— invariant: same dose history, different ISF multipliers → expected proportional responsetestSlicedAnnotatedDosesMatchStandard—sliced(from:to:)matches a freshannotate()over the same windowtestPrebuiltEffectsFastPathRunsWithoutError— smoke test for the pre-built-effects code pathglucoseEffectsMidAbsorptionISFproduces bit-identical output to single-threaded path (covered by existingLoopAlgorithmTests.swiftequivalence cases)